#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from pydantic import ValidationError
from pydantic.errors import PydanticUserError
from starlette.exceptions import HTTPException
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import JSONResponse
from conf.config import app_config
from core.exception.errors import BaseExceptionMixin
from core.logger import api_logger, admin_logger
from core.response import fail
from schemas.base_schema import CUSTOM_VALIDATION_ERROR_MESSAGES, CUSTOM_USAGE_ERROR_MESSAGES


async def _validation_exception_handler(e: RequestValidationError | ValidationError):
    """
    数据验证异常处理

    :param e:
    :return:
    """
    errors = []
    for error in e.errors():
        custom_message = CUSTOM_VALIDATION_ERROR_MESSAGES.get(error['type'])
        if custom_message:
            ctx = error.get('ctx')
            error['msg'] = custom_message.format(**ctx) if ctx else custom_message
        errors.append(error)
    error = errors[0]
    if error.get('type') == 'json_invalid':
        message = 'json解析失败'
    else:
        # error_input = error.get('input')
        field = str(error.get('loc')[-1])
        error_msg = error.get('msg')
        message = f'{field} {error_msg}'
        # message = f'{field} {error_msg}，输入：{error_input}'
    if isinstance(e, RequestValidationError):
        msg = f'请求参数非法: {message}'
    else:
        msg = f'参数非法: {message}'
    data = {'errors': errors} if app_config.APP_ENV == 'dev' else None
    return fail(status_code=200, msg=msg, data=data)


def register_exception(app: FastAPI):
    @app.exception_handler(HTTPException)
    async def http_exception_handler(request: Request, exc: HTTPException):
        """
        全局HTTP异常处理

        :param request:
        :param exc:
        :return:
        """

        return fail(code=-1, status_code=exc.status_code, msg=exc.detail)

    @app.exception_handler(RequestValidationError)
    async def fastapi_validation_exception_handler(request: Request, exc: RequestValidationError):
        """
        fastapi 数据验证异常处理

        :param request:
        :param exc:
        :return:
        """
        return await _validation_exception_handler(exc)

    @app.exception_handler(ValidationError)
    async def pydantic_validation_exception_handler(request: Request, exc: ValidationError):
        """
        pydantic 数据验证异常处理

        :param request:
        :param exc:
        :return:
        """
        return await _validation_exception_handler(exc)

    @app.exception_handler(PydanticUserError)
    async def pydantic_user_error_handler(request: Request, exc: PydanticUserError):
        """
        Pydantic 用户异常处理

        :param request:
        :param exc:
        :return:
        """
        return fail(msg=CUSTOM_USAGE_ERROR_MESSAGES.get(exc.code))

    @app.exception_handler(AssertionError)
    async def assertion_error_handler(request: Request, exc: AssertionError):
        """
        断言错误处理

        :param request:
        :param exc:
        :return:
        """
        return JSONResponse(
            status_code=500,
            content=fail(
                status_code=500,
                msg=str(''.join(exc.args) if exc.args else exc.__doc__),
            )
            if app_config.ENVIRONMENT == 'dev'
            else fail(status_code=500, msg='Internal Server Error'),
        )

    @app.exception_handler(Exception)
    async def all_exception_handler(request: Request, exc: Exception):
        """
        全局异常处理

        :param request:
        :param exc:
        :return:
        """
        if isinstance(exc, BaseExceptionMixin):
            return fail(
                status_code=exc.code,
                msg=str(exc.msg),
                data=exc.data if exc.data else None
            )
        else:
            return fail(status_code=500, msg=str(exc)) \
                if app_config.APP_ENV == 'dev' else fail(status_code=500, msg='Internal Server Error')

    @app.exception_handler(500)
    async def status_code_500_exception_handler(r, exc):
        """
        500 异常处理
        `Related issue <https://github.com/encode/starlette/issues/1175>`_
        :param r:
        :param exc:
        :return:
        """
        code = -1
        if isinstance(exc, BaseExceptionMixin):
            resp = fail(status_code=exc.status_code, code=exc.code, msg=str(exc.msg))
        else:
            code = 500
            resp = fail(status_code=500, msg=str(exc)) \
                if app_config.APP_ENV == 'dev' else fail(status_code=500, msg='Internal Server Error')

        method = r.method.upper()
        logger = api_logger if r.url.path.startswith('/api') else admin_logger
        logger.error(f'{r.state.request_id} | {r.client.host} | {method} {r.url} | code: {code} | exception: {exc}')

        return resp
